/**
   @file  packet.c
   @brief ENet packet management functions
 */
#include <string.h>
#define ENET_BUILDING_LIB 1
#include "enet/enet.h"

/** @defgroup Packet ENet packet functions
    @{
 */

/** Creates a packet that may be sent to a peer.
    @param dataContents initial contents of the packet's data; the packet's data will remain uninitialized if dataContents is NULL.
    @param dataLength   size of the data allocated for this packet
    @param flags        flags for this packet as described for the ENetPacket structure.
    @returns the packet on success, NULL on failure
 */
ENetPacket*
enet_packet_create(const void* data, size_t dataLength, enet_uint32 flags)
{
    ENetPacket* packet = (ENetPacket*)enet_malloc(sizeof(ENetPacket));

    if (flags & ENET_PACKET_FLAG_NO_ALLOCATE)
        packet->data = (enet_uint8*)data;
    else
    {
        packet->data = (enet_uint8*)enet_malloc(dataLength);
        if (packet->data == NULL)
        {
            enet_free(packet);
            return NULL;
        }

        if (data != NULL)
            memcpy(packet->data, data, dataLength);
    }

    packet->referenceCount = 0;
    packet->flags = flags;
    packet->dataLength = dataLength;
    packet->freeCallback = NULL;

    return packet;
}

/** Destroys the packet and deallocates its data.
    @param packet packet to be destroyed
 */
void
enet_packet_destroy(ENetPacket* packet)
{
    if (packet->freeCallback != NULL)
        (*packet->freeCallback)(packet);
    if (!(packet->flags & ENET_PACKET_FLAG_NO_ALLOCATE))
        enet_free(packet->data);
    enet_free(packet);
}

/** Attempts to resize the data in the packet to length specified in the
    dataLength parameter
    @param packet packet to resize
    @param dataLength new size for the packet data
    @returns 0 on success, < 0 on failure
 */
int
enet_packet_resize(ENetPacket* packet, size_t dataLength)
{
    enet_uint8* newData;

    if (dataLength <= packet->dataLength || (packet->flags & ENET_PACKET_FLAG_NO_ALLOCATE))
    {
        packet->dataLength = dataLength;

        return 0;
    }

    newData = (enet_uint8*)enet_malloc(dataLength);
    if (newData == NULL)
        return -1;

    memcpy(newData, packet->data, packet->dataLength);
    enet_free(packet->data);

    packet->data = newData;
    packet->dataLength = dataLength;

    return 0;
}

static int initializedCRC32 = 0;
static enet_uint32 crcTable[256];

static enet_uint32
reflect_crc(int val, int bits)
{
    int result = 0, bit;

    for (bit = 0; bit < bits; bit++)
    {
        if (val & 1) result |= 1 << (bits - 1 - bit);
        val >>= 1;
    }

    return result;
}

static void
initialize_crc32()
{
    int byte;

    for (byte = 0; byte < 256; ++byte)
    {
        enet_uint32 crc = reflect_crc(byte, 8) << 24;
        int offset;

        for (offset = 0; offset < 8; ++offset)
        {
            if (crc & 0x80000000)
                crc = (crc << 1) ^ 0x04c11db7;
            else
                crc <<= 1;
        }

        crcTable[byte] = reflect_crc(crc, 32);
    }

    initializedCRC32 = 1;
}

enet_uint32
enet_crc32(const ENetBuffer* buffers, size_t bufferCount)
{
    enet_uint32 crc = 0xFFFFFFFF;

    if (!initializedCRC32) initialize_crc32();

    while (bufferCount-- > 0)
    {
        const enet_uint8* data = (const enet_uint8*)buffers->data,
        * dataEnd = &data[buffers->dataLength];

        while (data < dataEnd)
        {
            crc = (crc >> 8) ^ crcTable[(crc & 0xFF) ^ *data++];
        }

        ++buffers;
    }

    return ENET_HOST_TO_NET_32(~crc);
}

/** @} */